/* ===========================================================
 * JFreeChart : a free chart library for the Java(tm) platform
 * ===========================================================
 *
 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jfreechart/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * ------------------
 * GanttRenderer.java
 * ------------------
 * (C) Copyright 2003-2008, by Object Refinery Limited.
 *
 * Original Author:  David Gilbert (for Object Refinery Limited);
 * Contributor(s):   -;
 *
 * Changes
 * -------
 * 16-Sep-2003 : Version 1 (DG);
 * 23-Sep-2003 : Fixed Checkstyle issues (DG);
 * 21-Oct-2003 : Bar width moved into CategoryItemRendererState (DG);
 * 03-Feb-2004 : Added get/set methods for attributes (DG);
 * 12-Aug-2004 : Fixed rendering problem with maxBarWidth attribute (DG);
 * 05-Nov-2004 : Modified drawItem() signature (DG);
 * 20-Apr-2005 : Renamed CategoryLabelGenerator
 *               --> CategoryItemLabelGenerator (DG);
 * 01-Dec-2005 : Fix for bug 1369954, drawBarOutline flag ignored (DG);
 * ------------- JFREECHART 1.0.x --------------------------------------------
 * 17-Jan-2006 : Set includeBaseInRange flag to false (DG);
 * 20-Mar-2007 : Implemented equals() and fixed serialization (DG);
 * 24-Jun-2008 : Added new barPainter mechanism (DG);
 * 26-Jun-2008 : Added crosshair support (DG);
 *
 */

package org.jfree.chart.renderer.category;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Stroke;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.event.RendererChangeEvent;
import org.jfree.chart.labels.CategoryItemLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.gantt.GanttCategoryDataset;
import org.jfree.data.gantt.TaskSeriesCollection;
import org.jfree.io.SerialUtilities;
import org.jfree.ui.RectangleEdge;
import org.jfree.util.PaintUtilities;

/**
 * A renderer for simple Gantt charts. The example shown here is generated by
 * the <code>GanttDemo1.java</code> program included in the JFreeChart Demo
 * Collection: <br>
 * <br>
 * <img src="../../../../../images/GanttRendererSample.png"
 * alt="GanttRendererSample.png" />
 */
public class GanttRenderer extends IntervalBarRenderer implements Serializable {

	/** For serialization. */
	private static final long serialVersionUID = -4010349116350119512L;

	/** The paint for displaying the percentage complete. */
	private transient Paint completePaint;

	/**
	 * Controls the starting edge of the progress indicator (expressed as a
	 * percentage of the overall bar width).
	 */
	private double startPercent;

	/**
	 * Controls the ending edge of the progress indicator (expressed as a
	 * percentage of the overall bar width).
	 */
	private double endPercent;

	/**
	 * Creates a new renderer.
	 */
	public GanttRenderer() {
		super();
		setIncludeBaseInRange(false);
		this.completePaint = Color.green;
		this.startPercent = 0.35;
		this.endPercent = 0.65;
	}

	/**
	 * Returns the paint used to show the percentage complete.
	 * 
	 * @return The paint (never <code>null</code>.
	 * 
	 * @see #setCompletePaint(Paint)
	 */
	public Paint getCompletePaint() {
		return this.completePaint;
	}

	/**
	 * Sets the paint used to show the percentage complete and sends a
	 * {@link RendererChangeEvent} to all registered listeners.
	 * 
	 * @param paint
	 *            the paint (<code>null</code> not permitted).
	 * 
	 * @see #getCompletePaint()
	 */
	public void setCompletePaint(Paint paint) {
		if (paint == null) {
			throw new IllegalArgumentException("Null 'paint' argument.");
		}
		this.completePaint = paint;
		fireChangeEvent();
	}

	/**
	 * Returns the position of the start of the progress indicator, as a
	 * percentage of the bar width.
	 * 
	 * @return The start percent.
	 * 
	 * @see #setStartPercent(double)
	 */
	public double getStartPercent() {
		return this.startPercent;
	}

	/**
	 * Sets the position of the start of the progress indicator, as a percentage
	 * of the bar width, and sends a {@link RendererChangeEvent} to all
	 * registered listeners.
	 * 
	 * @param percent
	 *            the percent.
	 * 
	 * @see #getStartPercent()
	 */
	public void setStartPercent(double percent) {
		this.startPercent = percent;
		fireChangeEvent();
	}

	/**
	 * Returns the position of the end of the progress indicator, as a
	 * percentage of the bar width.
	 * 
	 * @return The end percent.
	 * 
	 * @see #setEndPercent(double)
	 */
	public double getEndPercent() {
		return this.endPercent;
	}

	/**
	 * Sets the position of the end of the progress indicator, as a percentage
	 * of the bar width, and sends a {@link RendererChangeEvent} to all
	 * registered listeners.
	 * 
	 * @param percent
	 *            the percent.
	 * 
	 * @see #getEndPercent()
	 */
	public void setEndPercent(double percent) {
		this.endPercent = percent;
		fireChangeEvent();
	}

	/**
	 * Draws the bar for a single (series, category) data item.
	 * 
	 * @param g2
	 *            the graphics device.
	 * @param state
	 *            the renderer state.
	 * @param dataArea
	 *            the data area.
	 * @param plot
	 *            the plot.
	 * @param domainAxis
	 *            the domain axis.
	 * @param rangeAxis
	 *            the range axis.
	 * @param dataset
	 *            the dataset.
	 * @param row
	 *            the row index (zero-based).
	 * @param column
	 *            the column index (zero-based).
	 * @param pass
	 *            the pass index.
	 */
	public void drawItem(Graphics2D g2, CategoryItemRendererState state,
			Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
			ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
			int pass) {

		if (dataset instanceof GanttCategoryDataset) {
			GanttCategoryDataset gcd = (GanttCategoryDataset) dataset;
			drawTasks(g2, state, dataArea, plot, domainAxis, rangeAxis, gcd,
					row, column);
		} else { // let the superclass handle it...
			super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
					dataset, row, column, pass);
		}

	}

	/**
	 * Draws the tasks/subtasks for one item.
	 * 
	 * @param g2
	 *            the graphics device.
	 * @param state
	 *            the renderer state.
	 * @param dataArea
	 *            the data plot area.
	 * @param plot
	 *            the plot.
	 * @param domainAxis
	 *            the domain axis.
	 * @param rangeAxis
	 *            the range axis.
	 * @param dataset
	 *            the data.
	 * @param row
	 *            the row index (zero-based).
	 * @param column
	 *            the column index (zero-based).
	 */
	protected void drawTasks(Graphics2D g2, CategoryItemRendererState state,
			Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
			ValueAxis rangeAxis, GanttCategoryDataset dataset, int row,
			int column) {

		PlotOrientation orientation = plot.getOrientation();
		RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();

		int depCount = dataset.getPredIntervalCount(row, column);

		Number inicioBarra = dataset.getStartValue(row, column);

		double alturaBarra = calculateBarW0(plot, plot.getOrientation(),
				dataArea, domainAxis, state, row, column);

		for (int predinterval = 0; predinterval < depCount; predinterval++) {

			// value 0
			Number value0 = dataset.getEndPredValue(row, column, predinterval);
			if (value0 == null) {
				return;
			}
			double x0 = rangeAxis.valueToJava2D(value0.doubleValue(), dataArea,
					rangeAxisLocation);
			double y0 = domainAxis.getCategoryStart(
					dataset.getPredColumnIndex(row, column, predinterval),
					getColumnCount(), dataArea, plot.getDomainAxisEdge());

			// value 1
			double x1 = rangeAxis.valueToJava2D(inicioBarra.doubleValue(),
					dataArea, rangeAxisLocation);
			double y1 = y0;

			// value 2
			double x2 = x1;

			double y2;
			if (y1 < alturaBarra) {
				// aponta para baixo
				y2 = alturaBarra - (dataArea.getMinY() * .07);
			} else {
				// aponta para cima
				alturaBarra = alturaBarra + state.getBarWidth();
				y2 = alturaBarra + (dataArea.getMinY() * .07);
			}

			Path2D dependencyLine = new Path2D.Double();
			dependencyLine.moveTo(x0, y0);
			dependencyLine.lineTo(x1, y1);
			dependencyLine.lineTo(x2, y2);
			g2.setPaint(Color.BLACK);
			g2.draw(dependencyLine);

			Path2D arrow = new Path2D.Double();
			arrow.moveTo(x2, y2);

			double yinc = dataArea.getMinY() * .07;
			if (y1 < y2) {
				// apontado para baixo
				arrow.lineTo(x2 + (state.getBarWidth() * .1), y2 - yinc);
				arrow.lineTo(x2 - (state.getBarWidth() * .1), y2 - yinc);
			} else {
				// apontado para cima
				arrow.lineTo(x2 + (state.getBarWidth() * .15), y2 + yinc);
				arrow.lineTo(x2 - (state.getBarWidth() * .15), y2 + yinc);
			}
			arrow.closePath();
			g2.draw(arrow);
			g2.fill(arrow);
		}

		int count = dataset.getSubIntervalCount(row, column);
		if (count == 0) {
			drawTask(g2, state, dataArea, plot, domainAxis, rangeAxis, dataset,
					row, column);
			return;
		}

		double mimTranslatedValue = 99999999999999999999999999999.0;
		double maxTranslatedValue = 0.0;
		Number totalPercentComplete = 0.0;

		for (int subinterval = 0; subinterval < count; subinterval++) {

			// value 0
			Number value0 = dataset.getStartValue(row, column, subinterval);
			if (value0 == null) {
				return;
			}
			double translatedValue0 = rangeAxis.valueToJava2D(
					value0.doubleValue(), dataArea, rangeAxisLocation);

			// value 1
			Number value1 = dataset.getEndValue(row, column, subinterval);
			if (value1 == null) {
				return;
			}
			double translatedValue1 = rangeAxis.valueToJava2D(
					value1.doubleValue(), dataArea, rangeAxisLocation);

			if (mimTranslatedValue > translatedValue0) {
				mimTranslatedValue = translatedValue0;
			}

			if (maxTranslatedValue < translatedValue1) {
				maxTranslatedValue = translatedValue1;
			}

			Number percentConplete = dataset.getPercentComplete(row, column,
					subinterval);
			percentConplete = percentConplete == null ? 0.0 : percentConplete;
			double total = totalPercentComplete.doubleValue()
					+ percentConplete.doubleValue();
			totalPercentComplete = total;

		}

		totalPercentComplete = totalPercentComplete.equals(0.0) ? null
				: totalPercentComplete.doubleValue() / count;

		double rectStart = calculateBarW0(plot, plot.getOrientation(),
				dataArea, domainAxis, state, row, column);
		double rectLength = Math.abs(maxTranslatedValue - mimTranslatedValue);
		double rectBreadth = state.getBarWidth();

		// DRAW THE BARS...
		Rectangle2D bar = null;

		Path2D startPath = null;
		Path2D endPath = null;
		RectangleEdge barBase = null;
		if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
			bar = new Rectangle2D.Double(mimTranslatedValue, rectStart,
					rectLength, rectBreadth / 4);

			double x = mimTranslatedValue;
			double y = rectStart;

			startPath = new Path2D.Double();
			startPath.moveTo(x, y);
			startPath.lineTo(x, y + (rectBreadth / 1.5));
			startPath.lineTo(x + (rectLength * 0.06), y);
			startPath.closePath();

			x += rectLength;

			endPath = new Path2D.Double();
			endPath.moveTo(x, y);
			endPath.lineTo(x, y + (rectBreadth / 1.5));
			endPath.lineTo(x - (rectLength * 0.06), y);
			endPath.closePath();

			barBase = RectangleEdge.LEFT;
		} else if (plot.getOrientation() == PlotOrientation.VERTICAL) {
			bar = new Rectangle2D.Double(rectStart, mimTranslatedValue,
					rectBreadth, rectLength);

			double x = rectStart;
			double y = mimTranslatedValue;

			startPath = new Path2D.Double();
			startPath.moveTo(x, y);
			startPath.lineTo(x + (rectBreadth / 2), y);
			startPath.lineTo(x, y + (x * 0.25));
			startPath.closePath();

			x += rectLength;

			endPath = new Path2D.Double();
			endPath.moveTo(x, y);
			endPath.lineTo(x + (rectBreadth / 2), y);
			endPath.lineTo(x, y - (x * 0.06));
			endPath.closePath();

			barBase = RectangleEdge.BOTTOM;
		}

		Rectangle2D completeBar = null;
		Number percent = totalPercentComplete;
		double start = getStartPercent();
		double end = getEndPercent();
		if (percent != null) {
			double p = percent.doubleValue();
			if (orientation == PlotOrientation.HORIZONTAL) {
				completeBar = new Rectangle2D.Double(mimTranslatedValue,
						rectStart + start * rectBreadth, rectLength * p,
						rectBreadth * (end - start) / 3);
			} else if (orientation == PlotOrientation.VERTICAL) {
				completeBar = new Rectangle2D.Double(rectStart + start
						* rectBreadth, mimTranslatedValue + rectLength
						* (1 - p), rectBreadth * (end - start), rectLength * p);
			}

		}

		g2.setPaint(Color.BLACK);
		g2.fill(startPath);
		g2.fill(endPath);
		g2.fill(bar);

		if (completeBar != null) {
			g2.setPaint(getCompletePaint());
			g2.fill(completeBar);
		}

		if (isDrawBarOutline()
				&& state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
			g2.setStroke(getItemStroke(row, column));
			g2.setPaint(getItemOutlinePaint(row, column));
			g2.draw(bar);
		}

		// collect entity and tool tip information...
		if (state.getInfo() != null) {
			EntityCollection entities = state.getEntityCollection();
			if (entities != null) {
				addItemEntity(entities, dataset, row, column, bar);
			}
		}
	}

	/**
	 * Draws a single task.
	 * 
	 * @param g2
	 *            the graphics device.
	 * @param state
	 *            the renderer state.
	 * @param dataArea
	 *            the data plot area.
	 * @param plot
	 *            the plot.
	 * @param domainAxis
	 *            the domain axis.
	 * @param rangeAxis
	 *            the range axis.
	 * @param dataset
	 *            the data.
	 * @param row
	 *            the row index (zero-based).
	 * @param column
	 *            the column index (zero-based).
	 */
	protected void drawTask(Graphics2D g2, CategoryItemRendererState state,
			Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
			ValueAxis rangeAxis, GanttCategoryDataset dataset, int row,
			int column) {

		PlotOrientation orientation = plot.getOrientation();
		RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();

		// Y0
		Number value0 = dataset.getEndValue(row, column);
		if (value0 == null) {
			return;
		}
		double java2dValue0 = rangeAxis.valueToJava2D(value0.doubleValue(),
				dataArea, rangeAxisLocation);

		// Y1
		Number value1 = dataset.getStartValue(row, column);
		if (value1 == null) {
			return;
		}
		double java2dValue1 = rangeAxis.valueToJava2D(value1.doubleValue(),
				dataArea, rangeAxisLocation);

		if (java2dValue1 < java2dValue0) {
			double temp = java2dValue1;
			java2dValue1 = java2dValue0;
			java2dValue0 = temp;
			Number tempNum = value1;
			value1 = value0;
			value0 = tempNum;
		}

		double rectStart = calculateBarW0(plot, orientation, dataArea,
				domainAxis, state, row, column);

		Path2D mile = null;

		// Draw milestone (if so)
		if (dataset.getMilestone(row, column)) {

			double sideLenght = dataArea.getMinX() * .025;

			double x = java2dValue0;
			double y = rectStart + dataArea.getMinY() * .05;

			mile = new Path2D.Double();
			mile.moveTo(x, y);
			mile.lineTo(x + sideLenght, y + sideLenght);
			mile.lineTo(x, y + (2 * sideLenght));
			mile.lineTo(x - sideLenght, y + sideLenght);
			mile.closePath();

			g2.setPaint(Color.BLACK);
			g2.draw(mile);
			g2.fill(mile);

			// draw the task bar
		} else {
			double rectBreadth = state.getBarWidth();
			double rectLength = Math.abs(java2dValue1 - java2dValue0);

			Rectangle2D bar = null;
			RectangleEdge barBase = null;
			Rectangle2D completeBar = null;

			if (orientation == PlotOrientation.HORIZONTAL) {
				bar = new Rectangle2D.Double(java2dValue0, rectStart,
						rectLength, rectBreadth);
				barBase = RectangleEdge.LEFT;
			} else if (orientation == PlotOrientation.VERTICAL) {
				bar = new Rectangle2D.Double(rectStart, java2dValue1,
						rectBreadth, rectLength);
				barBase = RectangleEdge.BOTTOM;
			}

			Number percent = dataset.getPercentComplete(row, column);
			double start = getStartPercent();
			double end = getEndPercent();
			if (percent != null) {
				double p = percent.doubleValue();
				if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
					completeBar = new Rectangle2D.Double(java2dValue0,
							rectStart + start * rectBreadth, rectLength * p,
							rectBreadth * (end - start));
				} else if (plot.getOrientation() == PlotOrientation.VERTICAL) {
					completeBar = new Rectangle2D.Double(rectStart + start
							* rectBreadth, java2dValue1 + rectLength * (1 - p),
							rectBreadth * (end - start), rectLength * p);
				}

			}
			if (getShadowsVisible()) {
				getBarPainter().paintBarShadow(g2, this, row, column, bar,
						barBase, true);
			}
			getBarPainter().paintBar(g2, this, row, column, bar, barBase);

			if (completeBar != null) {
				g2.setPaint(getCompletePaint());
				g2.fill(completeBar);
			}

			// draw the outline...
			if (isDrawBarOutline()
					&& state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
				Stroke stroke = getItemOutlineStroke(row, column);
				Paint paint = getItemOutlinePaint(row, column);
				if (stroke != null && paint != null) {
					g2.setStroke(stroke);
					g2.setPaint(paint);
					g2.draw(bar);
				}
			}

			CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
					column);
			if (generator != null && isItemLabelVisible(row, column)) {
				drawItemLabel(g2, dataset, row, column, plot, generator, bar,
						false);
			}

			// collect entity and tool tip information...
			EntityCollection entities = state.getEntityCollection();
			if (entities != null) {
				addItemEntity(entities, dataset, row, column, bar);
			}

		}

		// drawn resources (if any)
		String resources="";
		int resourcecount = dataset.getResourceCount(row, column);
		for (int index = 0; index < resourcecount; index++) {

			resources += dataset.getResource(row, column, index);
			if(index+1 != resourcecount){
				resources+=", ";
			}
		}
		double constante = (resources.length()*(dataArea.getMinX()*.041128));
		Double x = java2dValue1 + (dataArea.getMinX() * .05);
		if(x+constante >= dataArea.getMaxX()){
			x = java2dValue0 - (constante+10);
		}
		Double y = rectStart + (state.getBarWidth());
		g2.setPaint(Color.BLACK);
		
		g2.drawString(resources , x.intValue(), y.intValue());
		

		// submit the current data point as a crosshair candidate
		int datasetIndex = plot.indexOf(dataset);
		Comparable columnKey = dataset.getColumnKey(column);
		Comparable rowKey = dataset.getRowKey(row);
		double xx = domainAxis.getCategorySeriesMiddle(columnKey, rowKey,
				dataset, getItemMargin(), dataArea, plot.getDomainAxisEdge());
		updateCrosshairValues(state.getCrosshairState(),
				dataset.getRowKey(row), dataset.getColumnKey(column),
				value1.doubleValue(), datasetIndex, xx, java2dValue1,
				orientation);

	}

	/**
	 * Returns the Java2D coordinate for the middle of the specified data item.
	 * 
	 * @param rowKey
	 *            the row key.
	 * @param columnKey
	 *            the column key.
	 * @param dataset
	 *            the dataset.
	 * @param axis
	 *            the axis.
	 * @param area
	 *            the drawing area.
	 * @param edge
	 *            the edge along which the axis lies.
	 * 
	 * @return The Java2D coordinate.
	 * 
	 * @since 1.0.11
	 */
	public double getItemMiddle(Comparable rowKey, Comparable columnKey,
			CategoryDataset dataset, CategoryAxis axis, Rectangle2D area,
			RectangleEdge edge) {
		return axis.getCategorySeriesMiddle(columnKey, rowKey, dataset,
				getItemMargin(), area, edge);
	}

	/**
	 * Tests this renderer for equality with an arbitrary object.
	 * 
	 * @param obj
	 *            the object (<code>null</code> permitted).
	 * 
	 * @return A boolean.
	 */
	public boolean equals(Object obj) {
		if (obj == this) {
			return true;
		}
		if (!(obj instanceof GanttRenderer)) {
			return false;
		}
		GanttRenderer that = (GanttRenderer) obj;
		if (!PaintUtilities.equal(this.completePaint, that.completePaint)) {
			return false;
		}
		if (this.startPercent != that.startPercent) {
			return false;
		}
		if (this.endPercent != that.endPercent) {
			return false;
		}
		return super.equals(obj);
	}

	/**
	 * Provides serialization support.
	 * 
	 * @param stream
	 *            the output stream.
	 * 
	 * @throws IOException
	 *             if there is an I/O error.
	 */
	private void writeObject(ObjectOutputStream stream) throws IOException {
		stream.defaultWriteObject();
		SerialUtilities.writePaint(this.completePaint, stream);
	}

	/**
	 * Provides serialization support.
	 * 
	 * @param stream
	 *            the input stream.
	 * 
	 * @throws IOException
	 *             if there is an I/O error.
	 * @throws ClassNotFoundException
	 *             if there is a classpath problem.
	 */
	private void readObject(ObjectInputStream stream) throws IOException,
			ClassNotFoundException {
		stream.defaultReadObject();
		this.completePaint = SerialUtilities.readPaint(stream);
	}

}
